{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.0.0\n",
      "sys.version_info(major=3, minor=7, micro=5, releaselevel='final', serial=0)\n",
      "numpy 1.16.4\n",
      "pandas 0.25.3\n",
      "sklearn 0.22\n",
      "tensorflow 2.0.0\n",
      "tensorflow_core.keras 2.2.4-tf\n",
      "Training Images ->  104908\n",
      "Validation Images ->  27800\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import pandas as pd \n",
    "import os\n",
    "import matplotlib.pyplot as plt\n",
    "import sklearn\n",
    "import sys\n",
    "import tensorflow as tf\n",
    "import time\n",
    "import random\n",
    "import pathlib\n",
    "\n",
    "from tensorflow import keras\n",
    "\n",
    "print(tf.__version__)\n",
    "print(sys.version_info)\n",
    "for module in np, pd, sklearn, tf, keras:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "\n",
    "training_path = pathlib.Path('./ban_face_dataset/training')\n",
    "validation_path = pathlib.Path('./ban_face_dataset/valid')\n",
    "\n",
    "train_image_paths = list(training_path.glob('*/*'))  \n",
    "valid_image_paths = list(validation_path.glob('*/*'))  \n",
    "\n",
    "train_image_paths = [str(path) for path in train_image_paths]\n",
    "valid_image_paths = [str(path) for path in valid_image_paths]\n",
    "\n",
    "random.shuffle(train_image_paths)\n",
    "random.shuffle(valid_image_paths)\n",
    "train_image_count = len(train_image_paths)\n",
    "valid_image_count = len(valid_image_paths)\n",
    "\n",
    "print(\"Training Images -> \", train_image_count)\n",
    "print(\"Validation Images -> \", valid_image_count)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['ban_face_dataset\\\\training\\\\Ornn\\\\630.jpg',\n",
       " 'ban_face_dataset\\\\training\\\\Irelia\\\\290.jpg',\n",
       " 'ban_face_dataset\\\\training\\\\Zyra\\\\560.jpg',\n",
       " 'ban_face_dataset\\\\training\\\\Illaoi\\\\233.jpg',\n",
       " 'ban_face_dataset\\\\training\\\\Annie\\\\190.jpg']"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_image_paths[:5]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['ban_face_dataset\\\\valid\\\\Karthus\\\\20.jpg',\n",
       " 'ban_face_dataset\\\\valid\\\\Braum\\\\150.jpg',\n",
       " 'ban_face_dataset\\\\valid\\\\Zoe\\\\135.jpg',\n",
       " 'ban_face_dataset\\\\valid\\\\Karma\\\\20.jpg',\n",
       " 'ban_face_dataset\\\\valid\\\\Twitch\\\\129.jpg']"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "valid_image_paths[:5]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['Aatrox',\n",
       " 'Ahri',\n",
       " 'Akali',\n",
       " 'Alistar',\n",
       " 'Amumu',\n",
       " 'Anivia',\n",
       " 'Annie',\n",
       " 'Aphelios',\n",
       " 'Ashe',\n",
       " 'Aurelion Sol',\n",
       " 'Azir',\n",
       " 'Bard',\n",
       " 'Blitzcrank',\n",
       " 'Brand',\n",
       " 'Braum',\n",
       " 'Caitlyn',\n",
       " 'Camille',\n",
       " 'Cassiopeia',\n",
       " \"Cho'Gath\",\n",
       " 'Corki',\n",
       " 'Darius',\n",
       " 'Diana',\n",
       " 'Dr. Mundo',\n",
       " 'Draven',\n",
       " 'Ekko',\n",
       " 'Elise',\n",
       " 'Evelynn',\n",
       " 'Ezreal',\n",
       " 'Fiddlesticks',\n",
       " 'Fiora',\n",
       " 'Fizz',\n",
       " 'Galio',\n",
       " 'Gangplank',\n",
       " 'Garen',\n",
       " 'Gnar',\n",
       " 'Gragas',\n",
       " 'Graves',\n",
       " 'Hecarim',\n",
       " 'Heimerdinger',\n",
       " 'Illaoi',\n",
       " 'Irelia',\n",
       " 'Ivern',\n",
       " 'Janna',\n",
       " 'Jarvan IV',\n",
       " 'Jax',\n",
       " 'Jayce',\n",
       " 'Jhin',\n",
       " 'Jinx',\n",
       " \"Kai'Sa\",\n",
       " 'Kalista',\n",
       " 'Karma',\n",
       " 'Karthus',\n",
       " 'Kassadin',\n",
       " 'Katarina',\n",
       " 'Kayle',\n",
       " 'Kayn',\n",
       " 'Kennen',\n",
       " \"Kha'Zix\",\n",
       " 'Kindred',\n",
       " 'Kled',\n",
       " \"Kog'Maw\",\n",
       " 'LeBlanc',\n",
       " 'Lee Sin',\n",
       " 'Leona',\n",
       " 'Lissandra',\n",
       " 'Lucian',\n",
       " 'Lulu',\n",
       " 'Lux',\n",
       " 'Malphite',\n",
       " 'Malzahar',\n",
       " 'Maokai',\n",
       " 'Master Yi',\n",
       " 'Miss Fortune',\n",
       " 'Mordekaiser',\n",
       " 'Morgana',\n",
       " 'Nami',\n",
       " 'Nasus',\n",
       " 'Nautilus',\n",
       " 'Neeko',\n",
       " 'Nidalee',\n",
       " 'Nocturne',\n",
       " 'None',\n",
       " 'None2',\n",
       " 'Nothing',\n",
       " 'Nunu & Willump',\n",
       " 'Olaf',\n",
       " 'Orianna',\n",
       " 'Ornn',\n",
       " 'Pantheon',\n",
       " 'Poppy',\n",
       " 'Pyke',\n",
       " 'Qiyana',\n",
       " 'Quinn',\n",
       " 'Rakan',\n",
       " 'Rammus',\n",
       " \"Rek'Sai\",\n",
       " 'Renekton',\n",
       " 'Rengar',\n",
       " 'Riven',\n",
       " 'Rumble',\n",
       " 'Ryze',\n",
       " 'Sejuani',\n",
       " 'Senna',\n",
       " 'Sett',\n",
       " 'Shaco',\n",
       " 'Shen',\n",
       " 'Shyvana',\n",
       " 'Singed',\n",
       " 'Sion',\n",
       " 'Sivir',\n",
       " 'Skarner',\n",
       " 'Sona',\n",
       " 'Soraka',\n",
       " 'Swain',\n",
       " 'Sylas',\n",
       " 'Syndra',\n",
       " 'Tahm Kench',\n",
       " 'Taliyah',\n",
       " 'Talon',\n",
       " 'Taric',\n",
       " 'Teemo',\n",
       " 'Thresh',\n",
       " 'Tristana',\n",
       " 'Trundle',\n",
       " 'Tryndamere',\n",
       " 'Twisted Fate',\n",
       " 'Twitch',\n",
       " 'Udyr',\n",
       " 'Urgot',\n",
       " 'Varus',\n",
       " 'Vayne',\n",
       " 'Veigar',\n",
       " \"Vel'Koz\",\n",
       " 'Vi',\n",
       " 'Viktor',\n",
       " 'Vladimir',\n",
       " 'Volibear',\n",
       " 'Warwick',\n",
       " 'Wukong',\n",
       " 'Xayah',\n",
       " 'Xerath',\n",
       " 'Xin Zhao',\n",
       " 'Yasuo',\n",
       " 'Yorick',\n",
       " 'Yuumi',\n",
       " 'Zac',\n",
       " 'Zed',\n",
       " 'Ziggs',\n",
       " 'Zilean',\n",
       " 'Zoe',\n",
       " 'Zyra']"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "label_names = sorted(item.name for item in training_path.glob('*/') if item.is_dir())\n",
    "label_names"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ban_face_dataset\\training\\Ornn\\630.jpg  --->   Ornn\n",
      "ban_face_dataset\\training\\Irelia\\290.jpg  --->   Irelia\n",
      "ban_face_dataset\\training\\Zyra\\560.jpg  --->   Zyra\n",
      "ban_face_dataset\\training\\Illaoi\\233.jpg  --->   Illaoi\n",
      "ban_face_dataset\\training\\Annie\\190.jpg  --->   Annie\n"
     ]
    }
   ],
   "source": [
    "training_image_labels = [pathlib.Path(path).parent.name for path in train_image_paths]\n",
    "for image, label in zip(train_image_paths[:5], training_image_labels[:5]):\n",
    "    print(image, ' --->  ', label)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_labels_info = []\n",
    "for image_path, label in zip(train_image_paths, training_image_labels):\n",
    "    train_labels_info.append((image_path, label))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('ban_face_dataset\\\\training\\\\Ornn\\\\630.jpg', 'Ornn'),\n",
      " ('ban_face_dataset\\\\training\\\\Irelia\\\\290.jpg', 'Irelia'),\n",
      " ('ban_face_dataset\\\\training\\\\Zyra\\\\560.jpg', 'Zyra'),\n",
      " ('ban_face_dataset\\\\training\\\\Illaoi\\\\233.jpg', 'Illaoi'),\n",
      " ('ban_face_dataset\\\\training\\\\Annie\\\\190.jpg', 'Annie')]\n"
     ]
    }
   ],
   "source": [
    "import pprint\n",
    "pprint.pprint(train_labels_info[:5])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ban_face_dataset\\valid\\Karthus\\20.jpg  --->   Karthus\n",
      "ban_face_dataset\\valid\\Braum\\150.jpg  --->   Braum\n",
      "ban_face_dataset\\valid\\Zoe\\135.jpg  --->   Zoe\n",
      "ban_face_dataset\\valid\\Karma\\20.jpg  --->   Karma\n",
      "ban_face_dataset\\valid\\Twitch\\129.jpg  --->   Twitch\n"
     ]
    }
   ],
   "source": [
    "valid_image_labels = [pathlib.Path(path).parent.name for path in valid_image_paths]\n",
    "for image, label in zip(valid_image_paths[:5], valid_image_labels[:5]):\n",
    "    print(image, ' --->  ', label)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "valid_labels_info = []\n",
    "for image_path, label in zip(valid_image_paths, valid_image_labels):\n",
    "    valid_labels_info.append((image_path, label))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('ban_face_dataset\\\\valid\\\\Karthus\\\\20.jpg', 'Karthus'),\n",
      " ('ban_face_dataset\\\\valid\\\\Braum\\\\150.jpg', 'Braum'),\n",
      " ('ban_face_dataset\\\\valid\\\\Zoe\\\\135.jpg', 'Zoe'),\n",
      " ('ban_face_dataset\\\\valid\\\\Karma\\\\20.jpg', 'Karma'),\n",
      " ('ban_face_dataset\\\\valid\\\\Twitch\\\\129.jpg', 'Twitch')]\n"
     ]
    }
   ],
   "source": [
    "pprint.pprint(valid_labels_info[:5])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                                   filepath   class\n",
      "0    ban_face_dataset\\training\\Ornn\\630.jpg    Ornn\n",
      "1  ban_face_dataset\\training\\Irelia\\290.jpg  Irelia\n",
      "2    ban_face_dataset\\training\\Zyra\\560.jpg    Zyra\n",
      "3  ban_face_dataset\\training\\Illaoi\\233.jpg  Illaoi\n",
      "4   ban_face_dataset\\training\\Annie\\190.jpg   Annie\n",
      "                                filepath    class\n",
      "0  ban_face_dataset\\valid\\Karthus\\20.jpg  Karthus\n",
      "1   ban_face_dataset\\valid\\Braum\\150.jpg    Braum\n",
      "2     ban_face_dataset\\valid\\Zoe\\135.jpg      Zoe\n",
      "3    ban_face_dataset\\valid\\Karma\\20.jpg    Karma\n",
      "4  ban_face_dataset\\valid\\Twitch\\129.jpg   Twitch\n"
     ]
    }
   ],
   "source": [
    "train_df = pd.DataFrame(train_labels_info)\n",
    "valid_df = pd.DataFrame(valid_labels_info)\n",
    "\n",
    "train_df.columns = valid_df.columns = ['filepath', 'class']\n",
    "\n",
    "print(train_df.head())\n",
    "print(valid_df.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Found 104908 validated image filenames belonging to 151 classes.\n",
      "Found 27800 validated image filenames belonging to 151 classes.\n"
     ]
    }
   ],
   "source": [
    "height = 32\n",
    "width = 32\n",
    "channels = 3\n",
    "batch_size = 128\n",
    "num_classes = 151\n",
    "\n",
    "train_datagen = keras.preprocessing.image.ImageDataGenerator(\n",
    "    #像素值 都除以255\n",
    "    rescale = 1./255,\n",
    "    # 图片随机旋转 (5度以内)\n",
    "    rotation_range = 20,\n",
    "    # 图片左右位移  20%限度以内\n",
    "    width_shift_range = 0.2,\n",
    "    # 图片上下位移  20%限度以内\n",
    "    height_shift_range = 0.2,\n",
    "    # 图像剪切强度\n",
    "    shear_range = 0.2,\n",
    "    # 图像缩放强度\n",
    "    zoom_range = 0.2,\n",
    "    # 是否水平翻转\n",
    "    horizontal_flip = False,\n",
    "    # 放大缩小吼， 像素填充方式\n",
    "    fill_mode = 'nearest',\n",
    ")\n",
    "\n",
    "train_generator = train_datagen.flow_from_dataframe(train_df, directory = './',\n",
    "                                                    x_col = 'filepath',\n",
    "                                                    y_col = 'class',\n",
    "                                                    classes = label_names,\n",
    "                                                    target_size = (height, width),\n",
    "                                                    batch_size = batch_size,\n",
    "                                                    seed = 2333,\n",
    "                                                    shuffle = True,\n",
    "                                                    class_mode = \"categorical\")\n",
    "\n",
    "valid_datagen = keras.preprocessing.image.ImageDataGenerator(\n",
    "    rescale = 1./255,\n",
    ")\n",
    "valid_generator = valid_datagen.flow_from_dataframe(valid_df, directory = './',\n",
    "                                                    x_col = 'filepath',\n",
    "                                                    y_col = 'class',\n",
    "                                                    classes = label_names,\n",
    "                                                    target_size = (height, width),\n",
    "                                                    batch_size = batch_size,\n",
    "                                                    seed = 666,\n",
    "                                                    shuffle = True,\n",
    "                                                    class_mode = \"categorical\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Training Generator Sample ->  104908\n",
      "Validation Generator Sample ->  27800\n"
     ]
    }
   ],
   "source": [
    "train_num = train_generator.samples\n",
    "valid_num = valid_generator.samples\n",
    "\n",
    "print(\"Training Generator Sample -> \", train_num)\n",
    "print(\"Validation Generator Sample -> \", valid_num)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(128, 32, 32, 3) (128, 151)\n",
      "[[0. 0. 0. ... 0. 0. 0.]\n",
      " [0. 0. 0. ... 0. 0. 0.]\n",
      " [0. 0. 0. ... 0. 0. 0.]\n",
      " ...\n",
      " [0. 0. 0. ... 0. 0. 0.]\n",
      " [0. 0. 0. ... 0. 0. 0.]\n",
      " [0. 0. 0. ... 0. 0. 0.]]\n",
      "(128, 32, 32, 3) (128, 151)\n",
      "[[0. 0. 0. ... 0. 0. 0.]\n",
      " [0. 0. 0. ... 0. 0. 0.]\n",
      " [0. 0. 0. ... 0. 0. 0.]\n",
      " ...\n",
      " [0. 0. 0. ... 0. 0. 0.]\n",
      " [0. 0. 0. ... 0. 0. 0.]\n",
      " [0. 0. 0. ... 0. 0. 0.]]\n"
     ]
    }
   ],
   "source": [
    "for i in range(2):\n",
    "    x, y = train_generator.next()\n",
    "    print(x.shape, y.shape)\n",
    "    print(y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"sequential\"\n",
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "conv2d (Conv2D)              (None, 32, 32, 16)        448       \n",
      "_________________________________________________________________\n",
      "conv2d_1 (Conv2D)            (None, 32, 32, 16)        2320      \n",
      "_________________________________________________________________\n",
      "max_pooling2d (MaxPooling2D) (None, 16, 16, 16)        0         \n",
      "_________________________________________________________________\n",
      "conv2d_2 (Conv2D)            (None, 16, 16, 32)        4640      \n",
      "_________________________________________________________________\n",
      "conv2d_3 (Conv2D)            (None, 16, 16, 32)        9248      \n",
      "_________________________________________________________________\n",
      "max_pooling2d_1 (MaxPooling2 (None, 8, 8, 32)          0         \n",
      "_________________________________________________________________\n",
      "conv2d_4 (Conv2D)            (None, 8, 8, 64)          18496     \n",
      "_________________________________________________________________\n",
      "conv2d_5 (Conv2D)            (None, 8, 8, 64)          36928     \n",
      "_________________________________________________________________\n",
      "max_pooling2d_2 (MaxPooling2 (None, 4, 4, 64)          0         \n",
      "_________________________________________________________________\n",
      "flatten (Flatten)            (None, 1024)              0         \n",
      "_________________________________________________________________\n",
      "dense (Dense)                (None, 128)               131200    \n",
      "_________________________________________________________________\n",
      "alpha_dropout (AlphaDropout) (None, 128)               0         \n",
      "_________________________________________________________________\n",
      "dense_1 (Dense)              (None, 151)               19479     \n",
      "=================================================================\n",
      "Total params: 222,759\n",
      "Trainable params: 222,759\n",
      "Non-trainable params: 0\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "model = keras.models.Sequential([\n",
    "     \n",
    "    keras.layers.Conv2D(filters=16, kernel_size = 3, padding='same',\n",
    "                       activation = 'selu', input_shape = [width, height, channels]),\n",
    "    keras.layers.Conv2D(filters=16, kernel_size = 3, \n",
    "                        padding='same', activation = 'selu'),\n",
    "    keras.layers.MaxPool2D(pool_size=2),\n",
    "    \n",
    "    keras.layers.Conv2D(filters=32, kernel_size = 3, \n",
    "                        padding='same', activation = 'selu'),\n",
    "    keras.layers.Conv2D(filters=32, kernel_size = 3, \n",
    "                        padding='same', activation = 'selu'),\n",
    "    keras.layers.MaxPool2D(pool_size=2),\n",
    "    \n",
    "    keras.layers.Conv2D(filters=64, kernel_size = 3, padding='same',\n",
    "                       activation = 'selu', input_shape = [width, height, channels]),\n",
    "    keras.layers.Conv2D(filters=64, kernel_size = 3, \n",
    "                        padding='same', activation = 'selu'),\n",
    "    keras.layers.MaxPool2D(pool_size=2),\n",
    "    \n",
    "    keras.layers.Flatten(),\n",
    "    keras.layers.Dense(128, activation = 'selu'),\n",
    "    keras.layers.AlphaDropout(rate=0.5),\n",
    "    \n",
    "    keras.layers.Dense(num_classes, activation = 'softmax')\n",
    "])\n",
    "\n",
    "model.compile(loss=\"categorical_crossentropy\",\n",
    "             optimizer = \"adam\", metrics = ['accuracy'])\n",
    "\n",
    "model.summary()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "logdir = './ban_face_recognition'\n",
    "if not os.path.exists(logdir):\n",
    "    os.mkdir(logdir)\n",
    "\n",
    "output_model_file = os.path.join(logdir, \"ban_face_recognition_model.h5\")\n",
    "\n",
    "callbacks = [\n",
    "    keras.callbacks.ModelCheckpoint(output_model_file, save_best_only=True),\n",
    "    keras.callbacks.EarlyStopping(patience=5, min_delta=1e-3)\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/30\n",
      "819/819 [==============================] - 108s 132ms/step - loss: 1.3520 - accuracy: 0.6594 - val_loss: 0.2844 - val_accuracy: 0.9454\n",
      "Epoch 2/30\n",
      "819/819 [==============================] - 106s 129ms/step - loss: 0.2882 - accuracy: 0.9143 - val_loss: 0.0219 - val_accuracy: 0.9931\n",
      "Epoch 3/30\n",
      "819/819 [==============================] - 106s 129ms/step - loss: 0.1894 - accuracy: 0.9432 - val_loss: 0.2166 - val_accuracy: 0.9692\n",
      "Epoch 4/30\n",
      "819/819 [==============================] - 105s 129ms/step - loss: 0.1525 - accuracy: 0.9533 - val_loss: 0.0236 - val_accuracy: 0.9945\n",
      "Epoch 5/30\n",
      "819/819 [==============================] - 105s 128ms/step - loss: 0.1318 - accuracy: 0.9610 - val_loss: 0.0609 - val_accuracy: 0.9892\n",
      "Epoch 6/30\n",
      "819/819 [==============================] - 108s 132ms/step - loss: 0.1248 - accuracy: 0.9630 - val_loss: 0.0046 - val_accuracy: 0.9989\n",
      "Epoch 7/30\n",
      "819/819 [==============================] - 106s 130ms/step - loss: 0.1062 - accuracy: 0.9689 - val_loss: 0.0063 - val_accuracy: 0.9987\n",
      "Epoch 8/30\n",
      "819/819 [==============================] - 106s 129ms/step - loss: 0.1100 - accuracy: 0.9688 - val_loss: 0.0061 - val_accuracy: 0.9983\n",
      "Epoch 9/30\n",
      "819/819 [==============================] - 106s 129ms/step - loss: 0.0957 - accuracy: 0.9727 - val_loss: 0.0508 - val_accuracy: 0.9953\n",
      "Epoch 10/30\n",
      "819/819 [==============================] - 106s 129ms/step - loss: 0.0926 - accuracy: 0.9744 - val_loss: 0.0103 - val_accuracy: 0.9975\n",
      "Epoch 11/30\n",
      "819/819 [==============================] - 106s 129ms/step - loss: 0.0956 - accuracy: 0.9747 - val_loss: 9.2642e-04 - val_accuracy: 0.9997\n",
      "Epoch 12/30\n",
      "819/819 [==============================] - 109s 133ms/step - loss: 0.0954 - accuracy: 0.9747 - val_loss: 0.0024 - val_accuracy: 0.9996\n",
      "Epoch 13/30\n",
      "819/819 [==============================] - 271s 331ms/step - loss: 0.0800 - accuracy: 0.9778 - val_loss: 0.0114 - val_accuracy: 0.9983\n",
      "Epoch 14/30\n",
      "819/819 [==============================] - 113s 137ms/step - loss: 0.0860 - accuracy: 0.9776 - val_loss: 0.0104 - val_accuracy: 0.9983\n",
      "Epoch 15/30\n",
      "819/819 [==============================] - 111s 136ms/step - loss: 0.0742 - accuracy: 0.9810 - val_loss: 0.0016 - val_accuracy: 0.9996\n",
      "Epoch 16/30\n",
      "819/819 [==============================] - 109s 134ms/step - loss: 0.0920 - accuracy: 0.9780 - val_loss: 0.1059 - val_accuracy: 0.9866\n",
      "Epoch 17/30\n",
      "819/819 [==============================] - 107s 131ms/step - loss: 0.0763 - accuracy: 0.9809 - val_loss: 0.0059 - val_accuracy: 0.9991\n",
      "Epoch 18/30\n",
      "819/819 [==============================] - 108s 131ms/step - loss: 0.0792 - accuracy: 0.9807 - val_loss: 0.0087 - val_accuracy: 0.9987\n",
      "Epoch 19/30\n",
      "819/819 [==============================] - 107s 131ms/step - loss: 0.0873 - accuracy: 0.9801 - val_loss: 0.0387 - val_accuracy: 0.9950\n",
      "Epoch 20/30\n",
      "819/819 [==============================] - 107s 130ms/step - loss: 0.0825 - accuracy: 0.9807 - val_loss: 0.0158 - val_accuracy: 0.9976\n",
      "Epoch 21/30\n",
      "819/819 [==============================] - 106s 130ms/step - loss: 0.0742 - accuracy: 0.9829 - val_loss: 0.0124 - val_accuracy: 0.9983\n",
      "Epoch 22/30\n",
      "819/819 [==============================] - 110s 134ms/step - loss: 0.0880 - accuracy: 0.9813 - val_loss: 0.0026 - val_accuracy: 0.9996\n",
      "Epoch 23/30\n",
      "819/819 [==============================] - 108s 132ms/step - loss: 0.0831 - accuracy: 0.9821 - val_loss: 0.0080 - val_accuracy: 0.9990\n",
      "Epoch 24/30\n",
      "819/819 [==============================] - 107s 131ms/step - loss: 0.0895 - accuracy: 0.9816 - val_loss: 4.3185e-06 - val_accuracy: 1.0000\n",
      "Epoch 25/30\n",
      "819/819 [==============================] - 107s 130ms/step - loss: 0.0727 - accuracy: 0.9844 - val_loss: 0.0264 - val_accuracy: 0.9976\n",
      "Epoch 26/30\n",
      "819/819 [==============================] - 107s 130ms/step - loss: 0.0827 - accuracy: 0.9829 - val_loss: 0.0074 - val_accuracy: 0.9990\n",
      "Epoch 27/30\n",
      "819/819 [==============================] - 106s 130ms/step - loss: 0.0801 - accuracy: 0.9838 - val_loss: 3.2258e-04 - val_accuracy: 0.9999\n",
      "Epoch 28/30\n",
      "819/819 [==============================] - 105s 129ms/step - loss: 0.0802 - accuracy: 0.9841 - val_loss: 0.0044 - val_accuracy: 0.9992\n",
      "Epoch 29/30\n",
      "819/819 [==============================] - 105s 128ms/step - loss: 0.0938 - accuracy: 0.9821 - val_loss: 2.5483e-07 - val_accuracy: 1.0000\n",
      "Epoch 30/30\n",
      "819/819 [==============================] - 108s 132ms/step - loss: 0.0769 - accuracy: 0.9853 - val_loss: 0.0120 - val_accuracy: 0.9986\n"
     ]
    }
   ],
   "source": [
    "epochs = 100\n",
    "#因为数据是generator 产生的 所以不能用fit函数\n",
    "history = model.fit_generator(train_generator, steps_per_epoch=train_num // batch_size,\n",
    "                             epochs=epochs, validation_data=valid_generator,\n",
    "                             validation_steps=valid_num//batch_size)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_learning_curves(history, label, epochs, min_value, max_value):\n",
    "    data = {}\n",
    "    data[label] = history.history[label]\n",
    "    data['val_'+label] = history.history['val_' + label]\n",
    "    \n",
    "    pd.DataFrame(data).plot(figsize = (8,5))\n",
    "    plt.grid(True)\n",
    "    plt.axis([0, epochs, min_value, max_value])\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAeoAAAEzCAYAAAD+XEDdAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8li6FKAAAgAElEQVR4nO3de3xcdZ3/8ddnLskkadqmbXpvaXExtLQNLZeyohKoIrgoimC5/FjoT+iPFVjF3++x+MALuOquy4o/ZVFqVUB2cbss2BX4sbDlElC5LCCVCpVSoTTpvaW5NplkZr6/P87JZJLmctJOOifN+/l4zONcZ+ab75yc95zvOXO+5pxDREREwilS6AKIiIhI/xTUIiIiIaagFhERCTEFtYiISIgpqEVEREJMQS0iIhJigwa1md1lZrvN7A/9LDczu93MNpvZa2a2JP/FFBERGZ2CHFHfA5wzwPJzgeP8x0rgzsMvloiIiECAoHbOPQu8N8Aq5wP3Os8LwHgzm5avAoqIiIxm+ThHPQOoy5mu9+eJiIjIYYrl4TWsj3l93pfUzFbiNY+TSCROmj17dh7e/siIZDoxMmQshrNo3l7XcETSHUQySSKZDqL+uLl03t4jn5xFgAjOuh+50964YS4DOMw5INNj2shAj6EDlzmE0pj/nl3DQ/qLul/NOX+6q9w9lxeOgRkOy47jHOZSgPeZpKMJ0tES0tEEmWjCX7dvmUyGSGTg+opmkkTT7URTbUQz7Vgmlc8/qCeL4LLbULTXMAIWxeF9Pt52lOmxXZnLdG9LPZYfqc8u9/PpnvY4vxi5Zcn5dAbdzrpe2x/HwOjeFnpMe+sc9P/l/+8dOb3LPNjfgb8P6Hp+d50Y9FFHOfP9eu5R99DP+/exXvZ/3R08nn3vg+suWTyRjqKKYNXRy6ZNm/Y65yqH8px8BHU9MCtneiawva8VnXOrgdUAVVVV7s033xzaO6U74fGvwN5NcMpVUHUuRPIXmgdxDrb8Bn77A9i8rnu+RaB0EpRPgTFTc4ZTYcyU7uGYKRBPeM/JZKBhC+x6A3a9Drtf94bvvd0dUrESmLwQJp8AU06AKfOhch6/fvEVPvTB03tusLn3aD9ovj/tvH9UbxjwkUl7z8l0QrIFkk2QbO716GtezjIcWBSKyiBeCvGS7mFRac68Mn/oLy8q9eogu05pz/He60V7br61tbXU1NTk9/PPpCHd4T86+xhPDrIPHGChcxCNQ7TIf8QhVtw9Hi2CSBz6C9XGenj3edjqP3a/ASQh2gbTl8Axfw6zPwCzToWS8dmnHVRPbfuh/mXY+gLUvQjbXoHOA96ysTNg1lLvMXupt21GogG2rZxlXV/EOtugvQHaGrqHbft7jTf2nN/RcvDfXTTG27a6hsXlPaeLxkBxznis2NseIzGv7JGoP+3Ps6hXx9nxrvkRXnr195yy9APe5xGJ9/xsuuYN8qUnkK76yqS9YVdZs4F/mFId0NkKHQe8z7ajxRvvaPXnt0Iq2b0txoogWpwznvOIFft10L382d88z4fPXObtG/NV5jDosQ9Iev/3qaS3zeX8Tw2Fmb071OfkI6gfAq4zszXAUqDRObcjD6/bU3sT/PuV8KcnoawS/u0yGH8MnLoSllwOiXH5e69MGjY+7AX09t9573fWV6FyHrTshOZdPYc7N0Dr7r6PChPjvOc37fD+IQAwqJjjhfGCz3jDySfAhLl9fvFIx0rz+/cNJ+cgk/L+kUc6M+/LQDQGlBa6NAcbNxMWXeQ9AA685wXtu895wf3cP8Fv/i9g3jY2+8/hmD+nrKUJXt3mrVv3Iuz5o/d8i8LUhbD4ci+UZy313qNPw/gFOVe60wtul/ZCN16an2AMqPWtJqisGv43Muv+kjAcYn7glhzaUeBgMtGi4T1oKpSQ7AMGDWoz+1egBphkZvXAzUAcwDm3CngU+DiwGTgArMh7KRu3wS8+C7s3widuhxMvgzf/H7xwJ/zXV6D27+HES2HpNTDxfYf+Pp1tsP4X3g5u/zsw4Vg47/tQfUn3kXF/Mmlo3ZsT4Dlh3rob/uwjMHk+TFkAk4/3vu0fjcyOjpAeiUoneK1MVed60x0HYNvL/lH3c962/dJPOAXgZbwvfzNPhQUXesE8fYl3JBom0TiMGVIr4WFLZxxtnWnaOtI0dTia2zspjkWJRw0bgUeLqXSG1mSalo4UrckULckULe3d463JFK0daZKdaWLRCEWxCEVdw1iE4l7TRdEIcX9Y7M/b25bh3X2tpDKOdMaRSvvDTIZ0xtHZazq7XsaRyTgyznmNCoBzLnu2wOHI+A2Fju518NeJRoxELEpJUZREPEIiHvUeufP88eJY5KDPzzlHMpWhqb2TprYUze2dNLen/EdndtjUnqIpZ/qSU2dz/olH7lKsQYPaOXfJIMsdcG3eStTbzj/AfRd5zaqX/Tv82TJv/vzzvcf2V+GFVfDy3fDfP4HjzobT/gqOrQneBHPgPXjpZ/DiKjiw19thffQbcPx5wb8lRqJeE3j5FNA173IY2jvT2Z1CU5u3k2j2dyTe/E5ak2kS8SjliRhjimOUFXvD8kSv8ekfoPSYDxGJmHd0uvM13vjNw8w/czlMqoJIhEzG0drh77wbm2luT3k79mQnLck0Le2dtHakaW5P0d7pXTth5p0N9Yb+tJl3Xq/Xsq71I2ZEzIhGvIcZRP3pSHYIkYgRNcsOoxHvVdL+Dj3jHOkMpJ3DOW+Hn854O/G06xrvXqcjlaHdD94D/rC9M01bZ5oDXeMd6Ww4d6R7tYw99V/Zv6ErnIrjUW88HqE4Fs0GWtejKBbBOXoEUjqTIZX2xnOn0z3W8cKsq64iEa/eoubXl19Xucu8B9l6ak2mafVDubk9RTJ1KNd/HIJnao/M+xyG4ljEC/BYlI50hub2TjrTA5+/N4MxRTHGlsQpT3j/V0e6d+h8NH0Pn81Pwv1XeOcD/ud/es1yvU1fDBf8GD76t/Dyz+Dlu+CfP+U1U592DSz8rHdOsy8NdfDCj+CVn3vN0sedDad/AY45/eg6zyKBJVNpWvxv1C1JLxhzp/ual8647ObihZZlw6trbm6g+XMAaO1IeUHc1ul9q29P0THIjjUaMUqLorR3pgfdyUD3jqasOMaYRIzO9qVE63bRktyWPZoKIh41EnH/i2uvo5+Dj3i8abLLyB41HWlF0QiJeITSoph/lBWltChKSTxKRWkRJUVRSuPRg5Yl4hHe3PQWx8x9Hx3pDMnONMlUJueRpiNnuiOVpiWZYl9Lho50xg/PCDH/i0nXMBGPEMudH7Ue60XNcHhfNJxzpJ13VJlx3UefBy3LOIqjjo/PLWJyWYSIH+zZIT2nzQ/33GVm1utotvszzf0cu45muz53cHR0dFJU5LWkdW3bubtQ82fkfnHrsSxXj2WD74f73AZ7bY+ujyP2rv/VSE59ePXTc17Xl86emti4sWnAciUSCWbOnEk8fvgtjOEN6t/9Mzz8BZg8Dy69H8YN0sxQPgXOvAk++CX4w4Pw4p3e85+4BU5a4V181vUaO/8Az90OGx7wtpiFF8EHrvfO48mQZDKO5mSKxgOd7D/QQUObd8SX7PR2Xu3ZnZs/7MzQnkr7y3N2fJ1p2lMZUumM39TlevyzZXL/Gf3xbHOZP689mSTx/JP9lnWgZsv2zjTNycFDEqAoFqE85+g1Fo1077xc3zs3l5NQXesAlBXHGFcSZ1ZFSfYb+9hEnLElccZmx2OUJ+LZ8ZJ4NPu3JFNp7+i3PUVzsjN7JNzjqLg9RXOyu6lz2842Zk0b2+NIfIwf4mXFMcr7nB+lOHb45yC7joC7gied8cPGn5fOBpHLOYL2npc9mswecXtH5Nl5ke6dbfeR58Cf+2Bqk1uo+fCxh/13HwnvvPMO5eXlTJw48Yg30Tc3N1NeXn5E3zPMnHPs27eP+vp65s6de9ivF76gdg6e+hb8+rvwvmVw0T2QGBv8+fEELL7MO2f97nPeEfNvv+9dGDb/fO+q5M1PeFccL73GayYfP2vw1x0hMhnH3tYkOxvb2dOcJOPnQ4+judyjupwjQW+Z9x02nXE0tnXS4Idvw4FOGtv8MPbHGw500NjWmX2PwXQ3G3rNhV1NiF3NheNK4sQj1uPbvuHtkC3nW79Bdgec2wS7a+dOpk6d1Od7D1RE5yARjzDGD8auZmNv2N3c1RVa+QisfPHqMcqEsqLAz/Gu+i7MnX7NvCNIyb/29nbmzJkzIs+jH23MjIkTJ7Jnz568vF64gjqVhF9dCxv+HZb8JfzF9w79wiQzmHO699i/xTt//bt7vZ8WnPU1OOVzw3YF5HBJptLsbkqys6mdHY3t7Gr0h03t7GhsY1dTkl1N7aSCJucQlCdijC+NM76kiPGlcWZNKGV8SZzxpXHGlcQZX1qUnS5PxLMXdngh7J3D6zqHNlxqa/dTU1M9rO8hEmYK6fDI52cRnqBu2w9r/ge8+xsvSD/0v/N3nrhiDnzs27DsZu93ftEj92en0hl2NLbTkkzR1ulduOI9Mt5FLSnvApZkKtPjIpf2Tq/Z+EBHird3tNHy63Xsa+046PVLi6JMHZdg2rgES4+dwLRxCaaOK2Hq2ASV5cXEItajqTXbJEt3c2z2V9g5N2YwMz94ixib8Jt3RUTkiAtHUO/f4l3ZvX8LXPDT7t+F5lssePPgUHSkMtTvP8C7+w6wZV9rj2H9/gOBLvgBrzm3pOvnBf7FLCVFUcYXGx+YO9UL4bGJbDBPGZegvDimb9EiMmqkUilisXBE15FS+L922yvwi+XeT0cuXwtzPljoEvWpvTPN1vcOsGVvqzfMCeRt+9t6nKctK4pyzMQy5k0r55wFUzlmQinjS+M5Adx9VWlJPEqxP93f7zS9c4p9XPEuIhIin/rUp6irq6O9vZ0vfOELrFy5kscee4ybbrqJdDrNpEmTePLJJ2lpaeH666/n5Zdfxsy4+eab+cxnPsOYMWNoafHuRvfAAw/wyCOPcM8993DllVcyYcIEXn31VZYsWcLy5cv54he/SFtbGyUlJdx9991UVVWRTqe58cYbefzxxzEzrr76aubPn88dd9zB2rVrAVi3bh133nknv/zlLwtZVUNS2KD+46Pw4OegbBJc+ShUvr+gxemyu7mdN7Y3sXFHMxt3NLFxRxNv720lnZPGYxMx5k4qY/GsCj594gyOmVjGnEmlHDOxjIllRTrKFZFR56677mLChAm0tbVxyimncP7553P11Vfz7LPPMnfuXN57z+uI8Zvf/Cbjxo1jw4YNAOzfv3/Q1960aRNPPPEE0WiUpqYmnn32WWKxGE888QQ33XQTDz74IKtXr+add97h1VdfJRaL8d5771FRUcG1117Lnj17qKys5O6772bFivzfl2s4FSyoizobYc2l3u+gL/03GDP5iJehM53h7T2t2TB+wx/ubek+Fzx9XIJ508bysROmctyUMV4gTyxlfOnwNKOLiByubzz8Om9sH/h3vkM1f/pYbv7EwD9hvf3227NHrnV1daxevZoPf/jD2Z8oTZgwAYAnnniCNWvWZJ9XUTH4hb0XXXQR0aj3i4vGxkauuOIK3nrrLcyMzs7O7Otec8012abxrve7/PLL+Zd/+RdWrFjB888/z7333juUP73gChbUxe17oGo5fOYnR+x2mpt2NfObt/Z6wbyziU07W7J3ISqKRjhuyhhqqiYzf9pY5k0by7xp5QpkEZEAamtreeKJJ3j++ecpLS2lpqaG6upq+up8yTnXZ6tj7rz29vYey8rKunPia1/7GmeeeSZr165ly5Yt2U5m+nvdFStW8IlPfIJEIsFFF1004s5xF6y0HUXjYfk/H5EbuW/Z28r31m3iod97nXpNGlPEvGljufL0OcybVs78aeM4trKMuK5sFpGjwGBHvsOhsbGRiooKSktL+eMf/8gLL7xAMpnkmWee4Z133sk2fU+YMIGzzz6bO+64g+9///uA1/RdUVHBlClT2LhxI1VVVaxdu7bfm6g0NjYyY4Z3A6t77rknO//ss89m1apV1NTUZJu+J0yYwPTp05k+fTrf+ta3WLduXZ+vGWYFC+pk8aRhD+ldTe3c/uRb/NtLdcSjET5f8z6u+MAcpowdpIMNEREZknPOOYdVq1axaNEiqqqqOO2006isrGT16tVccMEFZDIZJk+ezLp16/jqV7/Ktddey4IFC4hGo9x8881ccMEFfOc73+G8885j1qxZLFiwIHthWW9/8zd/wxVXXMH3vvc9zjrrrOz8q666ik2bNrFo0SLi8ThXX3011113HQCXXXYZe/bsYf78+UekPvLJcm9teCQdUn/UATUc6ODOZ/7Ez5/bQirtuHTpbK4768+YXD4yAzrv/SwfpVRPwaieghtJdbVx40bmzZtXkPceCbcQve6661i8eDGf+9znjth79vWZmNkrzrmTh/I6I6uhfhAHOlLc/dstrHrmT7QkU3zqxBnc8JH3M3tiCPsSFhGRI+Kkk06irKyM2267rdBFOSRHRVB3pDKseWkrtz+5mb0tST4ybzL/52NVHD91CPcIFxGRo9Irr7xS6CIclhEd1OmM46Hfb+N76zZR914bp86dwI8vX8JJx0wodNFERETyYkQGtXOOJzfu5h8ff5M3dzVzwvSx3LNiAWe8v1I3GhERkaPKiAvqTbua+fKDr/G7rQ3MnVTGP12ymL9YOI3IMPfMJCIiUggjLqi/+cgbvL23lb+/YCEXnjRTv30WEZGj2ogK6kzGsX5rA588cTqXnDq70MUREREZdiPqcPRPe1poTqY4cdb4QhdFREQOw5gxYwpdhBFjRAX1q3UNACyeraAWEZHDl0qlCl2EQY2ooF5f10B5Isaxk/RNTEQkTG688UZ+9KMfZadvueUWvvGNb7Bs2TKWLFnCwoUL+dWvfhXotVpaWvp93r333suiRYuorq7m8ssvB2DXrl18+tOfprq6murqap577jm2bNnCggULss/77ne/yy233AJATU0NN910E2eccQY/+MEPePjhh1m6dCmLFy/mIx/5CLt27cqWY8WKFSxcuJBFixbx4IMP8rOf/Ywbbrgh+7o/+clP+NKXvnTI9RbEiDpHvX5rA9Uzx+sKbxGRgfznl2Hnhvy+5tSFcO53+l188cUX88UvfpHPf/7zANx///089thj3HDDDYwdO5a9e/dy2mmn8clPfnLQn9EmEgnWrl170PPeeOMNvv3tb/Pb3/6WSZMmZfu3/uu//mvOOOMM1q5dSzqdpqWlZdA+rhsaGnjmmWcAr1OQF154ATPjpz/9Kbfeeiu33XZbn/1mFxUVsWjRIm699Vbi8Th33303P/7xjwNX46EYMUHd1pHmzV3N/NUZ7yt0UUREpJfFixeze/dutm/fzp49e6ioqGDatGnccMMNPPvss0QiEbZt28auXbuYOnXqgK/lnOOmm2466HlPPfUUF154IZMmTQK6+5t+6qmnsn1MR6NRxo0bN2hQL1++PDteX1/P8uXL2bFjBx0dHdn+s/vrN/uss87ikUceYd68eXR2drJw4cIh1tbQjJig3rCtkXTG6UIyEZHBDHDkO5wuvPBCHnjgAXbu3MnFF1/Mfffdx549e3jllVeIx+PMmTPnoH6m+9Lf8/rrb7ovsViMTCaTnR6of+vrr7+eL33pS3zyk5+ktrY220Te3/tdddVV/N3f/R3HH388K1asCFSewzFizlGvr/O+HZ2oC8lERELp4osvZs2aNTzwwANceOGFNDY2MnnyZOLxOE8//TTvvvtuoNfp73nLli3j/vvvZ9++fQDZpu9ly5Zx5513ApBOp2lqamLKlCns3r2bffv2kUwmeeSRRwZ8v67+rX/+859n53f1m92l6yh96dKl1NXV8Ytf/IJLLrkkaPUcshEU1A3MrChh0pjiQhdFRET6cMIJJ9Dc3MyMGTOYNm0al112GS+//DInn3wy9913H8cff3yg1+nveSeccAJf+cpXOOOMM6iurs5exPWDH/yAp59+moULF3LSSSfx+uuvE4/H+frXv87SpUs577zzBnzvW265hYsuuogPfehD2WZ1gK9+9avs37+fBQsWUF1dzdNPP51d9tnPfpbTTz892xw+nEZMf9Qf+PsnWXJMBXdcumQYSxVOI6lP3EJSPQWjegpuJNWV+qM+ss477zxuuOEGli1b1u86+eqPekQcUe9uamd7Y7vOT4uISEE1NDTw/ve/n5KSkgFDOp9GxMVkutGJiMjRZ8OGDdnfQncpLi7mxRdfLFCJBjd+/Hg2bdp0RN9zRAT1+roGYhHjhOnjCl0UERHJk4ULF7J+/fpCFyP0RkTT9/qtDcybNpZEPFroooiIhFahrjmSg+Xzswh9UKczjtfqG3R+WkRkAIlEgn379imsQ8A5x759+0gkEnl5vdA3fW/e3UJrR1pBLSIygJkzZ1JfX8+ePXuO+Hu3t7fnLZSOFolEgpkzZ+bltUIf1LrRiYjI4OLxePbWl0dabW0tixcvLsh7jwahb/peX9fA2ESMuRPLBl9ZRETkKBP6oH51awPVs9RjloiIjE6hDurWZIpNu5pZrPPTIiIySoU6qDdsayTjdH5aRERGr1AH9Xr/jmTVMxXUIiIyOoU7qLc2MHtCKRPVY5aIiIxS4Q7qOt3oRERERrfQBvXOxnZ2NqnHLBERGd1CG9S60YmIiEiIg/rVugbiUWP+tLGFLoqIiEjBBApqMzvHzN40s81m9uU+lo8zs4fN7Pdm9rqZrTjcgq3f2sB89ZglIiKj3KBBbWZR4IfAucB84BIzm99rtWuBN5xz1UANcJuZFR1qodIZx4ZtjTo/LSIio16QI+pTgc3Oubedcx3AGuD8Xus4oNzMDBgDvAekDrVQb+1u5kBHWuenRURk1AvSe9YMoC5nuh5Y2mudO4CHgO1AObDcOZfp/UJmthJYCVBZWUltbW2fb/hMXScAye2bqG3cHKCIR7eWlpZ+60q6qZ6CUT0Fp7oKRvU0vIIEdV+9YfTumfxjwHrgLOB9wDoz+7VzrqnHk5xbDawGqKqqcjU1NX2+4WMPvsb40p0s//iZeAfpo1ttbS391ZV0Uz0Fo3oKTnUVjOppeAVp+q4HZuVMz8Q7cs61Avil82wG3gGOP9RCra9roHrmeIW0iIiMekGC+iXgODOb618gdjFeM3eurcAyADObAlQBbx9Kgbp6zNKFZCIiIgGavp1zKTO7DngciAJ3OedeN7Nr/OWrgG8C95jZBrym8hudc3sPpUCv1avHLBERkS5BzlHjnHsUeLTXvFU549uBs/NRoK4es05Uj1kiIiLhuzPZ+rr9zJlYSkXZIf8MW0RE5KgRwqBWj1kiIiJdQhXUOxrb2NWUVFCLiIj4QhXU67f656dnVxS4JCIiIuEQrqCua6AoGmHetPJCF0VERCQUQhXUr9Y1MH/6WIpj6jFLREQEQhTUqXSGDfXqMUtERCRXaIJ6064W2jrTLNaNTkRERLJCE9RdNzqp1o1OREREskIU1PupKI1zzMTSQhdFREQkNEIU1A1Uz1KPWSIiIrlCEdTN7Z28tbtFF5KJiIj0Eoqg3lDfiHMoqEVERHoJRVC/2tVjloJaRESkh1AE9fq6BuZOKmN8qXrMEhERyVXwoHbOqccsERGRfhQ8qLc3trOnWT1miYiI9KXgQZ3tMUtBLSIicpDCB3XdfopiEeZNG1voooiIiIROCIK6gROmj6UoVvCiiIiIhE5B07EznWHDNvWYJSIi0p+CBvWbO5tp78woqEVERPpR0KDu6jFr8ayKQhZDREQktAoe1BPKipg1oaSQxRAREQmtggf1ieoxS0REpF8FC+qMgz/tUY9ZIiIiAylYUCfTqMcsERGRQRQwqB0A1QpqERGRfhX0iPrYyjLGlcQLVQQREZHQK1hQd6Sdmr1FREQGUbCgTjtYrKAWEREZUEF/nnWibnQiIiIyoIIFtQHHTysv1NuLiIiMCAUL6soSIx5Vj1kiIiIDKVhSlsZ1NzIREZHB6JBWREQkxBTUIiIiIaagFhERCTEFtYiISIgpqEVEREJMQS0iIhJiCmoREZEQU1CLiIiEmIJaREQkxBTUIiIiIRYoqM3sHDN708w2m9mX+1mnxszWm9nrZvZMfospIiIyOsUGW8HMosAPgY8C9cBLZvaQc+6NnHXGAz8CznHObTWzycNVYBERkdEkyBH1qcBm59zbzrkOYA1wfq91LgV+6ZzbCuCc253fYoqIiIxOQYJ6BlCXM13vz8v1fqDCzGrN7BUz+8t8FVBERGQ0G7TpG+irP0rXx+ucBCwDSoDnzewF59ymHi9kthJYCVBZWUltbe2QCzwatbS0qK4CUD0Fo3oKTnUVjOppeAUJ6npgVs70TGB7H+vsdc61Aq1m9ixQDfQIaufcamA1QFVVlaupqTnEYo8utbW1qK4Gp3oKRvUUnOoqGNXT8ArS9P0ScJyZzTWzIuBi4KFe6/wK+JCZxcysFFgKbMxvUUVEREafQY+onXMpM7sOeByIAnc55143s2v85auccxvN7DHgNSAD/NQ594fhLLiIiMhoEKTpG+fco8Cjveat6jX9j8A/5q9oIiIiojuTiYiIhJiCWkREJMQU1CIiIiGmoBYREQkxBbWIiEiIKahFRERCTEEtIiISYgpqERGREFNQi4iIhJiCWkREJMQU1CIiIiGmoBYREQkxBbWIiEiIKahFRERCTEEtIiISYgpqERGREFNQi4iIhJiCWkREJMQU1CIiIiGmoBYREQkxBbWIiEiIKahFRERCTEEtIiISYgpqERGREFNQi4iIhJiCWkREJMQU1CIiIiGmoBYREQkxBbWIiEiIKahFRERCTEEtIiISYgpqERGREFNQi4iIhJiCWkREJMQU1CIiIiGmoBYREQkxBbWIiEiIKahFRERCTEEtIiISYgpqERGREFNQi4iIhJiCWkREJMQU1CIiIiGmoBYREQkxBbWIiEiIBQpqMzvHzN40s81m9uUB1jvFzNJmdmH+iigiIjJ6DRrUZhYFfgicC8wHLjGz+f2s9w/A4/kupIiIyGgV5Ij6VGCzc+5t51wHsAY4v4/1rgceBHbnsXwiIiKjWpCgngHU5UzX+2XhuooAAAjpSURBVPOyzGwG8GlgVf6KJiIiIrEA61gf81yv6e8DNzrn0mZ9re6/kNlKYCVAZWUltbW1AYs5urW0tKiuAlA9BaN6Ck51FYzqaXgFCep6YFbO9Exge691TgbW+CE9Cfi4maWcc/+Ru5JzbjWwGqCqqsrV1NQcYrFHl9raWlRXg1M9BaN6Ck51FYzqaXgFCeqXgOPMbC6wDbgYuDR3Befc3K5xM7sHeKR3SIuIiMjQDRrUzrmUmV2HdzV3FLjLOfe6mV3jL9d5aRERkWES5Iga59yjwKO95vUZ0M65Kw+/WCIiIgK6M5mIiEioKahFRERCTEEtIiISYgpqERGREFNQi4iIhJiCWkREJMQU1CIiIiGmoBYREQkxBbWIiEiIKahFRERCTEEtIiISYgpqERGREFNQi4iIhJiCWkREJMQU1CIiIiGmoBYREQkxBbWIiEiIKahFRERCTEEtIiISYgpqERGREFNQi4iIhJiCWkREJMQU1CIiIiGmoBYREQkxBbWIiEiIKahFRERCTEEtIiISYgpqERGREFNQi4iIhJiCWkREJMQU1CIiIiGmoBYREQkxBbWIiEiIKahFRERCTEEtIiISYgpqERGREFNQi4iIhJiCWkREJMQU1CIiIiGmoBYREQkxBbWIiEiIKahFRERCTEEtIiISYgpqERGREFNQi4iIhJiCWkREJMQCBbWZnWNmb5rZZjP7ch/LLzOz1/zHc2ZWnf+iioiIjD6DBrWZRYEfAucC84FLzGx+r9XeAc5wzi0CvgmszndBRURERqMgR9SnApudc2875zqANcD5uSs4555zzu33J18AZua3mCIiIqNTLMA6M4C6nOl6YOkA638O+M++FpjZSmAlQGVlJbW1tcFKOcq1tLSorgJQPQWjegpOdRWM6ml4BQlq62Oe63NFszPxgvqDfS13zq3GbxavqqpyNTU1wUo5ytXW1qK6GpzqKRjVU3Cqq2BUT8MrSFDXA7NypmcC23uvZGaLgJ8C5zrn9uWneCIiIqNbkHPULwHHmdlcMysCLgYeyl3BzGYDvwQud85tyn8xRURERqdBj6idcykzuw54HIgCdznnXjeza/zlq4CvAxOBH5kZQMo5d/LwFVtERGR0CNL0jXPuUeDRXvNW5YxfBVyV36KJiIiI7kwmIiISYgpqERGREFNQi4iIhJiCWkREJMQU1CIiIiGmoBYREQkxBbWIiEiIKahFRERCTEEtIiISYgpqERGREFNQi4iIhJiCWkREJMQU1CIiIiGmoBYREQkxBbWIiEiIKahFRERCTEEtIiISYgpqERGREFNQi4iIhJiCWkREJMQU1CIiIiGmoBYREQkxBbWIiEiIKahFRERCTEEtIiISYgpqERGREFNQi4iIhJiCWkREJMQU1CIiIiGmoBYREQkxBbWIiEiIKahFRERCTEEtIiISYgpqERGREFNQi4iIhJiCWkREJMQU1CIiIiGmoBYREQkxBbWIiEiIKahFRERCTEEtIiISYgpqERGREFNQi4iIhJiCWkREJMQU1CIiIiEWKKjN7Bwze9PMNpvZl/tYbmZ2u7/8NTNbkv+iioiIjD6DBrWZRYEfAucC84FLzGx+r9XOBY7zHyuBO/NcThERkVEpyBH1qcBm59zbzrkOYA1wfq91zgfudZ4XgPFmNi3PZRURERl1ggT1DKAuZ7renzfUdURERGSIYgHWsT7muUNYBzNbidc0DpA0sz8EeH+BScDeQhdiBFA9BaN6Ck51FYzqKbiqoT4hSFDXA7NypmcC2w9hHZxzq4HVAGb2snPu5CGVdpRSXQWjegpG9RSc6ioY1VNwZvbyUJ8TpOn7JeA4M5trZkXAxcBDvdZ5CPhL/+rv04BG59yOoRZGREREehr0iNo5lzKz64DHgShwl3PudTO7xl++CngU+DiwGTgArBi+IouIiIweQZq+cc49ihfGufNW5Yw74NohvvfqIa4/mqmuglE9BaN6Ck51FYzqKbgh15V5GSsiIiJhpFuIioiIhFhBgnqwW5KKx8y2mNkGM1t/KFcKHs3M7C4z2537Ez8zm2Bm68zsLX9YUcgyhkE/9XSLmW3zt6v1ZvbxQpYxDMxslpk9bWYbzex1M/uCP1/bVC8D1JW2qxxmljCz/zaz3/v19A1//pC3qSPe9O3fknQT8FG8n3W9BFzinHvjiBZkBDCzLcDJzjn9PrEXM/sw0IJ3R7wF/rxbgfecc9/xvwBWOOduLGQ5C62feroFaHHOfbeQZQsT/06K05xzvzOzcuAV4FPAlWib6mGAuvos2q6yzMyAMudci5nFgd8AXwAuYIjbVCGOqIPcklRkQM65Z4H3es0+H/i5P/5zvJ3HqNZPPUkvzrkdzrnf+ePNwEa8uytqm+plgLqSHP4ttVv8ybj/cBzCNlWIoNbtRoNzwH+Z2Sv+Xd1kYFO6fr/vDycXuDxhdp3f091das7tyczmAIuBF9E2NaBedQXarnows6iZrQd2A+ucc4e0TRUiqAPdblQAON05twSvd7Jr/WZMkcN1J/A+4ERgB3BbYYsTHmY2BngQ+KJzrqnQ5QmzPupK21Uvzrm0c+5EvLt1nmpmCw7ldQoR1IFuNyrgnNvuD3cDa/FOG0j/dnX12uYPdxe4PKHknNvl70AywE/QdgWAfx7xQeA+59wv/dnapvrQV11pu+qfc64BqAXO4RC2qUIEdZBbko56ZlbmX6iBmZUBZwPqxGRgDwFX+ONXAL8qYFlCq1cXtJ9G21XXhT8/AzY6576Xs0jbVC/91ZW2q57MrNLMxvvjJcBHgD9yCNtUQW544l+2/326b0n67SNeiJAzs2PxjqLBu4PcL1RP3czsX4EavF57dgE3A/8B3A/MBrYCFznnRvWFVP3UUw1e86QDtgD/a7Tfm9/MPgj8GtgAZPzZN+Gde9U2lWOAuroEbVdZZrYI72KxKN5B8f3Oub81s4kMcZvSnclERERCTHcmExERCTEFtYiISIgpqEVEREJMQS0iIhJiCmoREZEQU1CLiIiEmIJaREQkxBTUIiIiIfb/AcY84emodbV9AAAAAElFTkSuQmCC\n",
      "text/plain": [
       "<Figure size 576x360 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_learning_curves(history, 'accuracy', epochs, 0, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 576x360 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_learning_curves(history, 'loss', epochs, 0, 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "def load_and_preprocess_single_img(path):\n",
    "    # read the img through file path\n",
    "    image = tf.io.read_file(path)  \n",
    "    image = tf.image.decode_jpeg(image, channels=3)\n",
    "    # 原始图片大小为(128, 128, 3)，重设为(32, 32)\n",
    "    image = tf.image.resize(image, [32, 32])  \n",
    "    image = tf.cast(image, tf.float32) / 255.0  # 归一化到[0,1]范围\n",
    "    image = np.expand_dims(image, axis = 0) # since you have batch_size, so you need to expand your image\n",
    "    return image"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "def evaluate_single_pic(path, show=False):\n",
    "    \n",
    "    if show:\n",
    "        import matplotlib.image as mpimg\n",
    "        plt.imshow(mpimg.imread(path))\n",
    "    image = load_and_preprocess_single_img(path)\n",
    "    predict_result = model.predict(image)\n",
    "    print(\"This is\", label_names[np.argmax(predict_result, axis=1)[0]])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.save_weights('./checkpoints/my_checkpoint')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.save('./ban_face_recognition_model.h5')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "# test_pic_path2 = \"./lol_data_1/test_2/18.png\"\n",
    "# evaluate_single_pic(test_pic_path2, True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "TensorFlow-GPU",
   "language": "python",
   "name": "tf2_gpu"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
